import Konva from 'konva'
import { KonvaEventObject } from 'konva/lib/Node'
import { defineComponent, h } from 'vue'

export default defineComponent({
  props: {
    containerName: { type: String, requires: true },
    imgUrl: { type: String, requires: true },
  },
  setup(props, { expose }) {
    let container: HTMLDivElement

    let stage: Konva.Stage
    let layer: Konva.Layer
    const image = new Image()
    let bgImg: Konva.Image
    const group: Konva.Group = new Konva.Group()

    let scale: number
    const scaleStep = 1.2

    /** 初始化 Konva */
    function initKonva(): Promise<void> {
      return new Promise<void>((resolve, reject) => {
        try {
          // 创建舞台
          stage = new Konva.Stage({ container })

          // 创建图层
          layer = new Konva.Layer({ draggable: false })

          // 加载背景图片
          image.src = props.imgUrl
          image.onload = function () {
            // 创建背景图形
            bgImg = new Konva.Image({ image: image })

            setImageView()

            // 图层挂载图形和事件
            layer.add(bgImg).add(group)

            layer.on('wheel', layerZoom)

            layer.off('wheel')

            // 背景图片拖拽边界
            layer.dragBoundFunc((pos) => {
              const width = stage.width() - bgImg.width() * layer.scaleX()
              const height = stage.height() - bgImg.height() * layer.scaleY()
              return {
                x: pos.x < width ? width : pos.x > 0 ? 0 : pos.x,
                y: pos.y < height ? height : pos.y > 0 ? 0 : pos.y,
              }
            })

            // 将图层添加到舞台
            stage.add(layer)
            resolve()
          }
        } catch (error) {
          reject(error)
        }
      })
    }

    /** 图层缩放方法 */
    function layerZoom({ evt }: KonvaEventObject<WheelEvent>): void {
      // 阻止默认事件
      evt.preventDefault()

      // 新的缩放比
      const newScale =
        evt.deltaY < 0
          ? layer.scale().x * scaleStep
          : Math.max(scale, layer.scale().x / scaleStep)
      if (newScale > 4) return

      // 旧的宽高
      const oldWidth = stage.width() * layer.scaleX()
      const oldHeight = stage.height() * layer.scaleY()

      // 新的宽高
      const newWidth = stage.width() * newScale
      const newHeight = stage.height() * newScale

      // 当前指针位置
      const pointer = layer.getRelativePointerPosition()

      // 设置图层位置
      layer.position({
        x: layer.x() - (newWidth - oldWidth) * (pointer.x / stage.width()),
        y: layer.y() - (newHeight - oldHeight) * (pointer.y / stage.height()),
      })

      // 重新设置缩放比
      layer.scale({ x: newScale, y: newScale })

      // 左侧缩放位置
      if (layer.x() > 0) layer.x(0)

      // 右侧缩放位置
      if (layer.x() + bgImg.width() * newScale < stage.width()) {
        layer.x(-(bgImg.width() * newScale - stage.width()))
      }

      // 顶部缩放位置
      if (layer.y() > 0) layer.y(0)

      // 底部缩放位置
      if (layer.y() + bgImg.height() * newScale < stage.height()) {
        layer.y(-(bgImg.height() * newScale - stage.height()))
      }
    }

    /** 设置图片显示 */
    function setImageView() {
      // 设置舞台宽高
      stage.size({
        width: container.offsetWidth,
        height: container.offsetHeight,
      })

      // 填满舞台的缩放比例
      scale =
        image.width / image.height < stage.width() / stage.height()
          ? stage.width() / image.width
          : stage.height() / image.height

      // 图层缩放到填满舞台
      layer.scale({ x: scale, y: scale })

      // 背景图片居中显示
      layer.position({
        x: stage.width() / 2 - (bgImg.width() * scale) / 2,
        y: stage.height() / 2 - (bgImg.height() * scale) / 2,
      })
    }

    /** ResizeObserver */
    const resizeObserver = new ResizeObserver(() => setImageView())

    /** 挂载 */
    async function onMount() {
      container = document.getElementById(props.containerName) as HTMLDivElement
      await initKonva()
      resizeObserver.observe(container)
      return { stage, layer, group }
    }

    /** 卸载 */
    function onUnmount() {
      resizeObserver.unobserve(container)
    }

    expose({ onMount, onUnmount })
  },

  render() {
    return h('div', { id: this.containerName, class: 'w-full h-full' })
  },
})
